package com.jd.dlc.limit.window;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.jd.dlc.constant.InitConstant;
import com.jd.dlc.domain.LimiterRule;
import com.jd.dlc.limit.Limiter;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @program: dlc
 * @ClassName WindowLimiter
 * @description:
 * @author: sizegang
 * @create: 2022-06-10
 **/
@Slf4j
public class WindowLimiter implements Limiter {

    private static final String APP = "win_key";

    private static final Integer windowSize = 5; // 滑动窗口5

    @Override
    public boolean isAllowed(String limitKey, long timeout, long limit) {
        SlidingWindow slidingWindow = slidingWindow(limitKey, limit);
        return !slidingWindow.addCount(1);
    }

    @Override
    public boolean refreshLimiter(LimiterRule limiterRule) {
        try {
            CaffeineCacheHolder.clearCacheByAppName(APP);
            log.error("limiter clear success");
        } catch (Exception ex) {
            log.error("limiter clear error{}", ex);
            return false;
        }
        return true;
    }

    public SlidingWindow slidingWindow(String limitKey, Long limit) {
        //取该key的滑窗
        return CaffeineCacheHolder.getCache(APP).get(limitKey, s -> {
            // 先获取key 如果没有则说明 当前key 没有配置规则 但是会分配一个
            return new SlidingWindow(windowSize, limit.intValue());
        });
    }


    public static class CaffeineCacheHolder {
        /**
         * key是appName，value是caffeine
         */
        private static Map<String, Cache<String, SlidingWindow>> CACHE_MAP = new ConcurrentHashMap<>();


        public static Cache<String, SlidingWindow> getCache(String appName) {
            if (CACHE_MAP.get(appName) == null) {
                synchronized (appName) {
                    Cache<String, SlidingWindow> cache = CaffeineBuilder.buildAllKeyCache();
                    if (CACHE_MAP.get(appName) != null) {
                        return CACHE_MAP.get(appName);
                    }
                    CACHE_MAP.put(appName, cache);
                    return cache;
                }
            }
            return CACHE_MAP.get(appName);
        }

        /**
         * 清空某个app的缓存key
         */
        public static void clearCacheByAppName(String appName) {
            if (CACHE_MAP.get(appName) != null) {
                CACHE_MAP.get(appName).invalidateAll();
            }
        }

        /**
         * 获取每个app的caffeine容量
         */
        public static Map<String, Integer> getSize() {
            Map<String, Integer> map = new HashMap<>();
            for (String appName : CACHE_MAP.keySet()) {
                Cache cache = CACHE_MAP.get(appName);
                Map caffMap = cache.asMap();
//            long bytes = ObjectSizeCalculator.getObjectSize(caffMap);
                map.put(appName, caffMap.size());
            }
            return map;
        }

    }

    public static class CaffeineBuilder {
        private static ExecutorService executorService = Executors.newFixedThreadPool(4);
        /**
         * 构建所有来的要缓存的key cache
         */
        public static Cache<String, SlidingWindow> buildAllKeyCache() {
            //老版本jdk1.8.0_20之前，caffeine默认的forkJoinPool在及其密集的淘汰过期时，会有forkJoinPool报错。建议用新版jdk
            return Caffeine.newBuilder()
                    .initialCapacity(8192)//初始大小
                    .maximumSize(5000000)//最大数量。这个数值我设置的很大，按30万每秒，每分钟是1800万，实际可以调小
                    .expireAfterWrite(InitConstant.caffeineMaxMinutes, TimeUnit.MINUTES)//过期时间，默认1分钟
                    .executor(executorService)
                    .softValues()
                    .build();
        }

        /**
         * 刚生成的热key，先放这里放几秒后，应该所有客户端都收到了热key并本地缓存了。这几秒内，不再处理同样的key了
         */
        public static Cache<String, SlidingWindow> buildRecentManualCache() {
            return Caffeine.newBuilder()
                    .initialCapacity(256)//初始大小
                    .maximumSize(50000)//最大数量
                    .expireAfterWrite(5, TimeUnit.SECONDS)//过期时间
                    .executor(executorService)
                    .softValues()
                    .build();
        }

        public static Cache<String, Object> buildRecentAutoCache() {
            return Caffeine.newBuilder()
                    .initialCapacity(256)//初始大小
                    .maximumSize(50000)//最大数量
                    .expireAfterWrite(5, TimeUnit.SECONDS)//过期时间
                    .executor(executorService)
                    .softValues()
                    .build();
        }


    }

}
